#pragma once

#include <cassert>

#include <cftc/timemeter/tracker.h>
#include <cftc/util/listentry.h>

#include "system/grid/grid.h"

namespace game::system::grid
{


    /**
     * @brief NGrid is nothing more than a wrapper of the Grid with an NxN cells
     *
     */
    class GridInfo
    {
    public:

        /**
         * @brief
         *
         */
        GridInfo()
            : i_timer(0), i_unloadActiveLockCount(0), i_unloadExplicitLock(false)
        {
        }

        /**
         * @brief
         *
         * @param expiry
         * @param unload
         */
        GridInfo(time_t expiry, bool unload = true)
            : i_timer(expiry), i_unloadActiveLockCount(0), i_unloadExplicitLock(!unload)
        {
        }

        /**
         * @brief
         *
         * @return const TimeTracker
         */
        const cftc::timemeter::Tracker& getTimeTracker() const { return i_timer; }

        /**
         * @brief
         *
         * @return bool
         */
        bool getUnloadLock() const
        {
            return i_unloadActiveLockCount || i_unloadExplicitLock;
        }

        /**
         * @brief
         *
         * @param on
         */
        void setUnloadExplicitLock(bool on) { i_unloadExplicitLock = on; }
        /**
         * @brief
         *
         */
        void incUnloadActiveLock() { ++i_unloadActiveLockCount; }
        /**
         * @brief
         *
         */
        void decUnloadActiveLock() { if (i_unloadActiveLockCount) { --i_unloadActiveLockCount; } }

        /**
         * @brief
         *
         * @param pTimer
         */
        void setTimer(const cftc::timemeter::Tracker& pTimer) { i_timer = pTimer; }
        /**
         * @brief
         *
         * @param interval
         */
        void ResetTimeTracker(time_t interval) { i_timer.Reset(interval); }
        /**
         * @brief
         *
         * @param diff
         */
        void UpdateTimeTracker(time_t diff) { i_timer.Update(diff); }

    private:

        cftc::timemeter::Tracker i_timer;                                /**< TODO */
        unsigned short i_unloadActiveLockCount : 16;                /**< lock from active object spawn points (prevent clone loading) */
        bool i_unloadExplicitLock : 1;                 /**< explicit manual lock or config setting */
    };

    /**
     * @brief
     *
     */
    enum GridState_e
    {
        GRID_STATE_INVALID = 0,
        GRID_STATE_ACTIVE = 1,
        GRID_STATE_IDLE = 2,
        GRID_STATE_REMOVAL = 3,
        MAX_GRID_STATE = 4
    };

    template
        <
        unsigned int N,
        class ACTIVE_OBJECT,
        class WORLD_OBJECT_TYPES,
        class GRID_OBJECT_TYPES
        >
        /**
         * @brief
         *
         */
        class NGrid
    {
    public:

        /**
         * @brief
         *
         */
        using GridType = Grid<ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES>;

        /**
         * @brief
         *
         * @param id
         * @param x
         * @param y
         * @param expiry
         * @param unload
         */
        NGrid(unsigned int id, unsigned int x, unsigned int y, time_t expiry, bool unload = true)
            : i_gridId(id), i_x(x), i_y(y), i_cellstate(GRID_STATE_INVALID), i_GridObjectDataLoaded(false)
        {
            i_GridInfo = GridInfo(expiry, unload);
            map_entry_ = std::make_shared<cftc::util::ListEntry<NGrid<N, ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES>*>>(this);
        }

        /**
         * @brief
         *
         * @param x
         * @param y
         * @return const GridType &operator
         */
        const GridType& operator()(unsigned int x, unsigned int y) const
        {
            assert(x < N);
            assert(y < N);
            return i_cells[x][y];
        }

        /**
         * @brief
         *
         * @param x
         * @param y
         * @return GridType &operator
         */
        GridType& operator()(unsigned int x, unsigned int y)
        {
            assert(x < N);
            assert(y < N);
            return i_cells[x][y];
        }

        /**
         * @brief
         *
         * @return const unsigned int
         */
        const unsigned int& GetGridId() const { return i_gridId; }
        /**
         * @brief
         *
         * @param id
         */
        void SetGridId(const unsigned int id) { i_gridId = id; }
        /**
         * @brief
         *
         * @return GridState_e
         */
        GridState_e GetGridState() const { return i_cellstate; }
        /**
         * @brief
         *
         * @param s
         */
        void SetGridState(GridState_e s) { i_cellstate = s; }
        /**
         * @brief
         *
         * @return unsigned int
         */
        unsigned int getX() const { return i_x; }
        /**
         * @brief
         *
         * @return unsigned int
         */
        unsigned int getY() const { return i_y; }

        /**
         * @brief
         *
         * @param GridRefManager<NGrid<N
         * @param ACTIVE_OBJECT
         * @param WORLD_OBJECT_TYPES
         * @param pTo
         */
#if 0
        void link(GridRefManager<NGrid<N, ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES> >* pTo)
        {
            i_Reference.link(pTo, this);
        }
#endif

        /**
         * @brief
         *
         * @return bool
         */
        bool isGridObjectDataLoaded() const { return i_GridObjectDataLoaded; }
        /**
         * @brief
         *
         * @param pLoaded
         */
        void setGridObjectDataLoaded(bool pLoaded) { i_GridObjectDataLoaded = pLoaded; }

        /**
         * @brief
         *
         * @return GridInfo
         */
        GridInfo* getGridInfoRef() { return &i_GridInfo; }
        /**
         * @brief
         *
         * @return const TimeTracker
         */
        const cftc::timemeter::Tracker& getTimeTracker() const { return i_GridInfo.getTimeTracker(); }
        /**
         * @brief
         *
         * @return bool
         */
        bool getUnloadLock() const { return i_GridInfo.getUnloadLock(); }
        /**
         * @brief
         *
         * @param on
         */
        void setUnloadExplicitLock(bool on) { i_GridInfo.setUnloadExplicitLock(on); }
        /**
         * @brief
         *
         */
        void incUnloadActiveLock() { i_GridInfo.incUnloadActiveLock(); }
        /**
         * @brief
         *
         */
        void decUnloadActiveLock() { i_GridInfo.decUnloadActiveLock(); }
        /**
         * @brief
         *
         * @param interval
         */
        void ResetTimeTracker(time_t interval) { i_GridInfo.ResetTimeTracker(interval); }
        /**
         * @brief
         *
         * @param diff
         */
        void UpdateTimeTracker(time_t diff) { i_GridInfo.UpdateTimeTracker(diff); }

        template<class SPECIFIC_OBJECT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param obj
         */
        void AddWorldObject(const unsigned int x, const unsigned int y, SPECIFIC_OBJECT* obj)
        {
            getGridType(x, y).AddWorldObject(obj);
        }

        template<class SPECIFIC_OBJECT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param obj
         */
        void RemoveWorldObject(const unsigned int x, const unsigned int y, SPECIFIC_OBJECT* obj)
        {
            getGridType(x, y).RemoveWorldObject(obj);
        }

#if 0
        template<class T, class TT>
        /**
         * @brief
         *
         * @param TypeContainerVisitor<T
         * @param visitor
         */
        void Visit(TypeContainerVisitor<T, TT>& visitor)
        {
            for (unsigned int x = 0; x < N; ++x)
                for (unsigned int y = 0; y < N; ++y)
                {
                    i_cells[x][y].Visit(visitor);
                }
        }
#endif

#if 0
        template<class T, class TT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param TypeContainerVisitor<T
         * @param visitor
         */
        void Visit(const unsigned int& x, const unsigned int& y, TypeContainerVisitor<T, TT>& visitor)
        {
            getGridType(x, y).Visit(visitor);
        }
#endif

        /**
         * @brief
         *
         * @return unsigned int
         */
        unsigned int ActiveObjectsInGrid() const
        {
            unsigned int count = 0;
            for (unsigned int x = 0; x < N; ++x)
                for (unsigned int y = 0; y < N; ++y)
                {
#if 0
                    count += i_cells[x][y].ActiveObjectsInGrid();
#endif
                }

            return count;
        }

        template<class SPECIFIC_OBJECT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param obj
         * @return bool
         */
        bool AddGridObject(const unsigned int x, const unsigned int y, SPECIFIC_OBJECT* obj)
        {
            return getGridType(x, y).AddGridObject(obj);
        }

        template<class SPECIFIC_OBJECT>
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param obj
         * @return bool
         */
        bool RemoveGridObject(const unsigned int x, const unsigned int y, SPECIFIC_OBJECT* obj)
        {
            return getGridType(x, y).RemoveGridObject(obj);
        }

#if 1
        std::shared_ptr<cftc::util::ListEntry<NGrid<N, ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES>*>> map_entry_;
#endif
    private:

        /**
         * @brief
         *
         * @param x
         * @param y
         * @return GridType
         */
        GridType& getGridType(const unsigned int& x, const unsigned int& y)
        {
            assert(x < N);
            assert(y < N);
            return i_cells[x][y];
        }

        unsigned int i_gridId; /**< TODO */
        GridInfo i_GridInfo; /**< TODO */
#if 0
        GridReference<NGrid<N, ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES> > i_Reference; /**< TODO */
#endif
        unsigned int i_x; /**< TODO */
        unsigned int i_y; /**< TODO */
        GridState_e i_cellstate; /**< TODO */
        GridType i_cells[N][N]; /**< TODO */
        bool i_GridObjectDataLoaded; /**< TODO */
    };

}
